function uninstallSubcommand(named: string | string[]): Fig.Subcommand {
  return {
    name: named,
    description: "Uninstall a package",
    args: {
      name: "package",
      generators: dependenciesGenerator,
      filterStrategy: "fuzzy",
      isVariadic: true,
    },
    options: npmUninstallOptions,
  };
}

const atsInStr = (s: string) => (s.match(/@/g) || []).length;

export const createNpmSearchHandler =
  (keywords?: string[]) =>
  async (
    context: string[],
    executeShellCommand: Fig.ExecuteCommandFunction,
    shellContext: Fig.ShellContext
  ): Promise<Fig.Suggestion[]> => {
    const searchTerm = context[context.length - 1];
    if (searchTerm === "") {
      return [];
    }
    // Add optional keyword parameter
    const keywordParameter =
      keywords && keywords.length > 0 ? `+keywords:${keywords.join(",")}` : "";

    const queryPackagesUrl = keywordParameter
      ? `https://api.npms.io/v2/search?size=20&q=${searchTerm}${keywordParameter}`
      : `https://api.npms.io/v2/search/suggestions?q=${searchTerm}&size=20`;

    // Query the API with the package name
    const queryPackages = [
      "-s",
      "-H",
      "Accept: application/json",
      queryPackagesUrl,
    ];
    // We need to remove the '@' at the end of the searchTerm before querying versions
    const queryVersions = [
      "-s",
      "-H",
      "Accept: application/vnd.npm.install-v1+json",
      `https://registry.npmjs.org/${searchTerm.slice(0, -1)}`,
    ];
    // If the end of our token is '@', then we want to generate version suggestions
    // Otherwise, we want packages
    const out = (query: string) =>
      executeShellCommand({
        command: "curl",
        args: query[query.length - 1] === "@" ? queryVersions : queryPackages,
      });
    // If our token starts with '@', then a 2nd '@' tells us we want
    // versions.
    // Otherwise, '@' anywhere else in the string will indicate the same.
    const shouldGetVersion = searchTerm.startsWith("@")
      ? atsInStr(searchTerm) > 1
      : searchTerm.includes("@");

    try {
      const data = JSON.parse((await out(searchTerm)).stdout);
      if (shouldGetVersion) {
        // create dist tags suggestions
        const versions = Object.entries(data["dist-tags"] || {}).map(
          ([key, value]) => ({
            name: key,
            description: value,
          })
        ) as Fig.Suggestion[];
        // create versions
        versions.push(
          ...Object.keys(data.versions)
            .map((version) => ({ name: version }) as Fig.Suggestion)
            .reverse()
        );
        return versions;
      }

      const results = keywordParameter ? data.results : data;
      return results.map(
        (item: { package: { name: string; description: string } }) => ({
          name: item.package.name,
          description: item.package.description,
        })
      ) as Fig.Suggestion[];
    } catch (error) {
      console.error({ error });
      return [];
    }
  };

// GENERATORS
export const npmSearchGenerator: Fig.Generator = {
  trigger: (newToken, oldToken) => {
    // If the package name starts with '@', we want to trigger when
    // the 2nd '@' is typed because we'll need to generate version
    // suggetsions
    // e.g. @typescript-eslint/types
    if (oldToken.startsWith("@")) {
      return !(atsInStr(oldToken) > 1 && atsInStr(newToken) > 1);
    }

    // If the package name doesn't start with '@', then trigger when
    // we see the first '@' so we can generate version suggestions
    return !(oldToken.includes("@") && newToken.includes("@"));
  },
  getQueryTerm: "@",
  cache: {
    ttl: 1000 * 60 * 60 * 24 * 2, // 2 days
  },
  custom: createNpmSearchHandler(),
};

const workspaceGenerator: Fig.Generator = {
  // script: "cat $(npm prefix)/package.json",
  custom: async (tokens, executeShellCommand) => {
    const { stdout: npmPrefix } = await executeShellCommand({
      command: "npm",
      // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays
      args: ["prefix"],
    });

    const { stdout: out } = await executeShellCommand({
      command: "cat",
      // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays
      args: [`${npmPrefix}/package.json`],
    });

    const suggestions: Fig.Suggestion[] = [];
    try {
      if (out.trim() == "") {
        return suggestions;
      }

      const packageContent = JSON.parse(out);
      const workspaces = packageContent["workspaces"];

      if (workspaces) {
        for (const workspace of workspaces) {
          suggestions.push({
            name: workspace,
            description: "Workspaces",
          });
        }
      }
    } catch (e) {
      console.log(e);
    }
    return suggestions;
  },
};

/** Generator that lists package.json dependencies */
export const dependenciesGenerator: Fig.Generator = {
  trigger: (newToken) => newToken === "-g" || newToken === "--global",
  custom: async function (tokens, executeShellCommand) {
    if (!tokens.includes("-g") && !tokens.includes("--global")) {
      const { stdout: npmPrefix } = await executeShellCommand({
        command: "npm",
        // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays
        args: ["prefix"],
      });
      const { stdout: out } = await executeShellCommand({
        command: "cat",
        // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays
        args: [`${npmPrefix}/package.json`],
      });
      const packageContent = JSON.parse(out);
      const dependencies = packageContent["dependencies"] ?? {};
      const devDependencies = packageContent["devDependencies"];
      const optionalDependencies = packageContent["optionalDependencies"] ?? {};
      Object.assign(dependencies, devDependencies, optionalDependencies);

      return Object.keys(dependencies)
        .filter((pkgName) => {
          const isListed = tokens.some((current) => current === pkgName);
          return !isListed;
        })
        .map((pkgName) => ({
          name: pkgName,
          icon: "📦",
          description: dependencies[pkgName]
            ? "dependency"
            : optionalDependencies[pkgName]
              ? "optionalDependency"
              : "devDependency",
        }));
    } else {
      const { stdout } = await executeShellCommand({
        command: "bash",
        args: ["-c", "ls -1 `npm root -g`"],
      });
      return stdout.split("\n").map((name) => ({
        name,
        icon: "📦",
        description: "Global dependency",
      }));
    }
  },
};

/** Generator that lists package.json scripts (with the respect to the `fig` field) */
export const npmScriptsGenerator: Fig.Generator = {
  cache: {
    strategy: "stale-while-revalidate",
    cacheByDirectory: true,
  },
  script: [
    "bash",
    "-c",
    "until [[ -f package.json ]] || [[ $PWD = '/' ]]; do cd ..; done; cat package.json",
  ],
  postProcess: function (out, [npmClient]) {
    if (out.trim() == "") {
      return [];
    }

    try {
      const packageContent = JSON.parse(out);
      const scripts = packageContent["scripts"];
      const figCompletions = packageContent["fig"] || {};

      if (scripts) {
        return Object.entries(scripts).map(([scriptName, scriptContents]) => {
          const icon =
            npmClient === "yarn"
              ? "fig://icon?type=yarn"
              : "fig://icon?type=npm";
          const customScripts: Fig.Suggestion = figCompletions[scriptName];
          return {
            name: scriptName,
            icon,
            description: scriptContents as string,
            priority: 51,
            /**
             * If there are custom definitions for the scripts
             * we want to override the default values
             * */
            ...customScripts,
          };
        });
      }
    } catch (e) {
      console.error(e);
    }

    return [];
  },
};

const globalOption: Fig.Option = {
  name: ["-g", "--global"],
  description:
    "Operates in 'global' mode, so that packages are installed into the prefix folder instead of the current working directory",
};

const jsonOption: Fig.Option = {
  name: "--json",
  description: "Show output in json format",
};

const omitOption: Fig.Option = {
  name: "--omit",
  description: "Dependency types to omit from the installation tree on disk",
  args: {
    name: "Package type",
    default: "dev",
    suggestions: ["dev", "optional", "peer"],
  },
  isRepeatable: 3,
};

const parseableOption: Fig.Option = {
  name: ["-p", "--parseable"],
  description:
    "Output parseable results from commands that write to standard output",
};

const longOption: Fig.Option = {
  name: ["-l", "--long"],
  description: "Show extended information",
};

const workSpaceOptions: Fig.Option[] = [
  {
    name: ["-w", "--workspace"],
    description:
      "Enable running a command in the context of the configured workspaces of the current project",
    args: {
      name: "workspace",
      generators: workspaceGenerator,
      isVariadic: true,
    },
  },
  {
    name: ["-ws", "--workspaces"],
    description:
      "Enable running a command in the context of all the configured workspaces",
  },
];

const npmUninstallOptions: Fig.Option[] = [
  {
    name: ["-S", "--save"],
    description: "Package will be removed from your dependencies",
  },
  {
    name: ["-D", "--save-dev"],
    description: "Package will appear in your `devDependencies`",
  },
  {
    name: ["-O", "--save-optional"],
    description: "Package will appear in your `optionalDependencies`",
  },
  {
    name: "--no-save",
    description: "Prevents saving to `dependencies`",
  },
  {
    name: "-g",
    description: "Uninstall global package",
  },
  ...workSpaceOptions,
];

const npmListOptions: Fig.Option[] = [
  {
    name: ["-a", "-all"],
    description: "Show all outdated or installed packages",
  },
  jsonOption,
  longOption,
  parseableOption,
  {
    name: "--depth",
    description: "The depth to go when recursing packages",
    args: { name: "depth" },
  },
  {
    name: "--link",
    description: "Limits output to only those packages that are linked",
  },
  {
    name: "--package-lock-only",
    description:
      "Current operation will only use the package-lock.json, ignoring node_modules",
  },
  {
    name: "--no-unicode",
    description: "Uses unicode characters in the tree output",
  },
  globalOption,
  omitOption,
  ...workSpaceOptions,
];

const registryOption: Fig.Option = {
  name: "--registry",
  description: "The base URL of the npm registry",
  args: { name: "registry" },
};

const verboseOption: Fig.Option = {
  name: "--verbose",
  description: "Show extra information",
  args: { name: "verbose" },
};

const otpOption: Fig.Option = {
  name: "--otp",
  description: "One-time password from a two-factor authenticator",
  args: { name: "otp" },
};

const ignoreScriptsOption: Fig.Option = {
  name: "--ignore-scripts",
  description:
    "If true, npm does not run scripts specified in package.json files",
};

const scriptShellOption: Fig.Option = {
  name: "--script-shell",
  description:
    "The shell to use for scripts run with the npm exec, npm run and npm init <pkg> commands",
  args: { name: "script-shell" },
};

const dryRunOption: Fig.Option = {
  name: "--dry-run",
  description:
    "Indicates that you don't want npm to make any changes and that it should only report what it would have done",
};

const completionSpec: Fig.Spec = {
  name: "npm",
  parserDirectives: {
    flagsArePosixNoncompliant: true,
  },
  description: "Node package manager",
  subcommands: [
    {
      name: ["install", "i", "add"],
      description: "Install a package and its dependencies",
      args: {
        name: "package",
        isOptional: true,
        generators: npmSearchGenerator,
        debounce: true,
        isVariadic: true,
      },
      options: [
        {
          name: ["-P", "--save-prod"],
          description:
            "Package will appear in your `dependencies`. This is the default unless `-D` or `-O` are present",
        },
        {
          name: ["-D", "--save-dev"],
          description: "Package will appear in your `devDependencies`",
        },
        {
          name: ["-O", "--save-optional"],
          description: "Package will appear in your `optionalDependencies`",
        },
        {
          name: "--no-save",
          description: "Prevents saving to `dependencies`",
        },
        {
          name: ["-E", "--save-exact"],
          description:
            "Saved dependencies will be configured with an exact version rather than using npm's default semver range operator",
        },
        {
          name: ["-B", "--save-bundle"],
          description:
            "Saved dependencies will also be added to your bundleDependencies list",
        },
        globalOption,
        {
          name: "--global-style",
          description:
            "Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder",
        },
        {
          name: "--legacy-bundling",
          description:
            "Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package",
        },
        {
          name: "--legacy-peer-deps",
          description:
            "Bypass peerDependency auto-installation. Emulate install behavior of NPM v4 through v6",
        },
        {
          name: "--strict-peer-deps",
          description:
            "If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure",
        },
        {
          name: "--no-package-lock",
          description: "Ignores package-lock.json files when installing",
        },
        registryOption,
        verboseOption,
        omitOption,
        ignoreScriptsOption,
        {
          name: "--no-audit",
          description:
            "Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes",
        },
        {
          name: "--no-bin-links",
          description:
            "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables",
        },
        {
          name: "--no-fund",
          description:
            "Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding",
        },
        dryRunOption,
        ...workSpaceOptions,
      ],
    },
    {
      name: ["run", "run-script"],
      description: "Run arbitrary package scripts",
      options: [
        ...workSpaceOptions,
        {
          name: "--if-present",
          description:
            "Npm will not exit with an error code when run-script is invoked for a script that isn't defined in the scripts section of package.json",
        },
        {
          name: "--silent",
          description: "",
        },
        ignoreScriptsOption,
        scriptShellOption,
        {
          name: "--",
          args: {
            name: "args",
            isVariadic: true,
            // TODO: load the spec based on the runned script (see yarn spec `yarnScriptParsedDirectives`)
          },
        },
      ],
      args: {
        name: "script",
        description: "Script to run from your package.json",
        filterStrategy: "fuzzy",
        generators: npmScriptsGenerator,
      },
    },
    {
      name: "init",
      description: "Trigger the initialization",
      options: [
        {
          name: ["-y", "--yes"],
          description:
            "Automatically answer 'yes' to any prompts that npm might print on the command line",
        },
        {
          name: "-w",
          description:
            "Create the folders and boilerplate expected while also adding a reference to your project workspaces property",
          args: { name: "dir" },
        },
      ],
    },
    { name: "access", description: "Set access controls on private packages" },
    {
      name: ["adduser", "login"],
      description: "Add a registry user account",
      options: [
        registryOption,
        {
          name: "--scope",
          description:
            "Associate an operation with a scope for a scoped registry",
          args: {
            name: "scope",
            description: "Scope name",
          },
        },
      ],
    },
    {
      name: "audit",
      description: "Run a security audit",
      subcommands: [
        {
          name: "fix",
          description:
            "If the fix argument is provided, then remediations will be applied to the package tree",
          options: [
            dryRunOption,
            {
              name: ["-f", "--force"],
              description:
                "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input",
              isDangerous: true,
            },
            ...workSpaceOptions,
          ],
        },
      ],
      options: [
        ...workSpaceOptions,
        {
          name: "--audit-level",
          description:
            "The minimum level of vulnerability for npm audit to exit with a non-zero exit code",
          args: {
            name: "audit",
            suggestions: [
              "info",
              "low",
              "moderate",
              "high",
              "critical",
              "none",
            ],
          },
        },
        {
          name: "--package-lock-only",
          description:
            "Current operation will only use the package-lock.json, ignoring node_modules",
        },
        jsonOption,
        omitOption,
      ],
    },
    {
      name: "bin",
      description: "Print the folder where npm will install executables",
      options: [globalOption],
    },
    {
      name: ["bugs", "issues"],
      description: "Report bugs for a package in a web browser",
      args: {
        name: "package",
        isOptional: true,
        generators: npmSearchGenerator,
        debounce: true,
        isVariadic: true,
      },
      options: [
        {
          name: "--no-browser",
          description: "Display in command line instead of browser",
          exclusiveOn: ["--browser"],
        },
        {
          name: "--browser",
          description:
            "The browser that is called by the npm bugs command to open websites",
          args: { name: "browser" },
          exclusiveOn: ["--no-browser"],
        },
        registryOption,
      ],
    },
    {
      name: "cache",
      description: "Manipulates packages cache",
      subcommands: [
        {
          name: "add",
          description: "Add the specified packages to the local cache",
        },
        {
          name: "clean",
          description: "Delete all data out of the cache folder",
        },
        {
          name: "verify",
          description:
            "Verify the contents of the cache folder, garbage collecting any unneeded data, and verifying the integrity of the cache index and all cached data",
        },
      ],
      options: [
        {
          name: "--cache",
          args: { name: "cache" },
          description: "The location of npm's cache directory",
        },
      ],
    },
    {
      name: ["ci", "clean-install", "install-clean"],
      description: "Install a project with a clean slate",
      options: [
        {
          name: "--audit",
          description:
            'When "true" submit audit reports alongside the current npm command to the default registry and all registries configured for scopes',
          args: {
            name: "audit",
            suggestions: ["true", "false"],
          },
          exclusiveOn: ["--no-audit"],
        },
        {
          name: "--no-audit",
          description:
            "Do not submit audit reports alongside the current npm command",
          exclusiveOn: ["--audit"],
        },
        ignoreScriptsOption,
        scriptShellOption,
        verboseOption,
        registryOption,
      ],
    },
    {
      name: "cit",
      description: "Install a project with a clean slate and run tests",
    },
    {
      name: "clean-install-test",
      description: "Install a project with a clean slate and run tests",
    },
    { name: "completion", description: "Tab completion for npm" },
    {
      name: ["config", "c"],
      description: "Manage the npm configuration files",
      subcommands: [
        {
          name: "set",
          description: "Sets the config key to the value",
          args: [{ name: "key" }, { name: "value" }],
          options: [
            { name: ["-g", "--global"], description: "Sets it globally" },
          ],
        },
        {
          name: "get",
          description: "Echo the config value to stdout",
          args: { name: "key" },
        },
        {
          name: "list",
          description: "Show all the config settings",
          options: [
            { name: "-g", description: "Lists globally installed packages" },
            { name: "-l", description: "Also shows defaults" },
            jsonOption,
          ],
        },
        {
          name: "delete",
          description: "Deletes the key from all configuration files",
          args: { name: "key" },
        },
        {
          name: "edit",
          description: "Opens the config file in an editor",
          options: [
            { name: "--global", description: "Edits the global config" },
          ],
        },
      ],
    },
    { name: "create", description: "Create a package.json file" },
    {
      name: ["dedupe", "ddp"],
      description: "Reduce duplication in the package tree",
    },
    {
      name: "deprecate",
      description: "Deprecate a version of a package",
      options: [registryOption],
    },
    { name: "dist-tag", description: "Modify package distribution tags" },
    {
      name: ["docs", "home"],
      description: "Open documentation for a package in a web browser",
      args: {
        name: "package",
        isOptional: true,
        generators: npmSearchGenerator,
        debounce: true,
        isVariadic: true,
      },
      options: [
        ...workSpaceOptions,
        registryOption,
        {
          name: "--no-browser",
          description: "Display in command line instead of browser",
          exclusiveOn: ["--browser"],
        },
        {
          name: "--browser",
          description:
            "The browser that is called by the npm docs command to open websites",
          args: { name: "browser" },
          exclusiveOn: ["--no-browser"],
        },
      ],
    },
    {
      name: "doctor",
      description: "Check your npm environment",
      options: [registryOption],
    },
    {
      name: "edit",
      description: "Edit an installed package",
      options: [
        {
          name: "--editor",
          description: "The command to run for npm edit or npm config edit",
        },
      ],
    },
    {
      name: "explore",
      description: "Browse an installed package",
      args: {
        name: "package",
        filterStrategy: "fuzzy",
        generators: dependenciesGenerator,
      },
    },
    { name: "fund", description: "Retrieve funding information" },
    { name: "get", description: "Echo the config value to stdout" },
    {
      name: "help",
      description: "Get help on npm",
      args: {
        name: "term",
        isVariadic: true,
        description: "Terms to search for",
      },
      options: [
        {
          name: "--viewer",
          description: "The program to use to view help content",
          args: {
            name: "viewer",
          },
        },
      ],
    },
    {
      name: "help-search",
      description: "Search npm help documentation",
      args: {
        name: "text",
        description: "Text to search for",
      },
      options: [longOption],
    },
    { name: "hook", description: "Manage registry hooks" },
    {
      name: "install-ci-test",
      description: "Install a project with a clean slate and run tests",
    },
    { name: "install-test", description: "Install package(s) and run tests" },
    { name: "it", description: "Install package(s) and run tests" },
    {
      name: "link",
      description: "Symlink a package folder",
      args: { name: "path", template: "filepaths" },
    },
    { name: "ln", description: "Symlink a package folder" },
    {
      name: "logout",
      description: "Log out of the registry",
      options: [
        registryOption,
        {
          name: "--scope",
          description:
            "Associate an operation with a scope for a scoped registry",
          args: {
            name: "scope",
            description: "Scope name",
          },
        },
      ],
    },
    {
      name: ["ls", "list"],
      description: "List installed packages",
      options: npmListOptions,
      args: { name: "[@scope]/pkg", isVariadic: true },
    },
    {
      name: "org",
      description: "Manage orgs",
      subcommands: [
        {
          name: "set",
          description: "Add a user to an org or manage roles",
          args: [
            {
              name: "orgname",
              description: "Organization name",
            },
            {
              name: "username",
              description: "User name",
            },
            {
              name: "role",
              isOptional: true,
              suggestions: ["developer", "admin", "owner"],
            },
          ],
          options: [registryOption, otpOption],
        },
        {
          name: "rm",
          description: "Remove a user from an org",
          args: [
            {
              name: "orgname",
              description: "Organization name",
            },
            {
              name: "username",
              description: "User name",
            },
          ],
          options: [registryOption, otpOption],
        },
        {
          name: "ls",
          description:
            "List users in an org or see what roles a particular user has in an org",
          args: [
            {
              name: "orgname",
              description: "Organization name",
            },
            {
              name: "username",
              description: "User name",
              isOptional: true,
            },
          ],
          options: [registryOption, otpOption, jsonOption, parseableOption],
        },
      ],
    },
    {
      name: "outdated",
      description: "Check for outdated packages",
      args: {
        name: "[<@scope>/]<pkg>",
        isVariadic: true,
        isOptional: true,
      },
      options: [
        {
          name: ["-a", "-all"],
          description: "Show all outdated or installed packages",
        },
        jsonOption,
        longOption,
        parseableOption,
        {
          name: "-g",
          description: "Checks globally",
        },
        ...workSpaceOptions,
      ],
    },
    {
      name: ["owner", "author"],
      description: "Manage package owners",
      subcommands: [
        {
          name: "ls",
          description:
            "List all the users who have access to modify a package and push new versions. Handy when you need to know who to bug for help",
          args: { name: "[@scope/]pkg" },
          options: [registryOption],
        },
        {
          name: "add",
          description:
            "Add a new user as a maintainer of a package. This user is enabled to modify metadata, publish new versions, and add other owners",
          args: [{ name: "user" }, { name: "[@scope/]pkg" }],
          options: [registryOption, otpOption],
        },
        {
          name: "rm",
          description:
            "Remove a user from the package owner list. This immediately revokes their privileges",
          args: [{ name: "user" }, { name: "[@scope/]pkg" }],
          options: [registryOption, otpOption],
        },
      ],
    },
    {
      name: "pack",
      description: "Create a tarball from a package",
      args: {
        name: "[<@scope>/]<pkg>",
      },
      options: [
        jsonOption,
        dryRunOption,
        ...workSpaceOptions,
        {
          name: "--pack-destination",
          description: "Directory in which npm pack will save tarballs",
          args: {
            name: "pack-destination",
            template: ["folders"],
          },
        },
      ],
    },
    {
      name: "ping",
      description: "Ping npm registry",
      options: [registryOption],
    },
    {
      name: "pkg",
      description: "Manages your package.json",
      subcommands: [
        {
          name: "get",
          description:
            "Retrieves a value key, defined in your package.json file. It is possible to get multiple values and values for child fields",
          args: {
            name: "field",
            description:
              "Name of the field to get. You can view child fields by separating them with a period",
            isVariadic: true,
          },
          options: [jsonOption, ...workSpaceOptions],
        },
        {
          name: "set",
          description:
            "Sets a value in your package.json based on the field value. It is possible to set multiple values and values for child fields",
          args: {
            // Format is <field>=<value>. How to achieve this?
            name: "field",
            description:
              "Name of the field to set. You can set child fields by separating them with a period",
            isVariadic: true,
          },
          options: [
            jsonOption,
            ...workSpaceOptions,
            {
              name: ["-f", "--force"],
              description:
                "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg",
              isDangerous: true,
            },
          ],
        },
        {
          name: "delete",
          description: "Deletes a key from your package.json",
          args: {
            name: "key",
            description:
              "Name of the key to delete. You can delete child fields by separating them with a period",
            isVariadic: true,
          },
          options: [
            ...workSpaceOptions,
            {
              name: ["-f", "--force"],
              description:
                "Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input. Allow clobbering existing values in npm pkg",
              isDangerous: true,
            },
          ],
        },
      ],
    },
    {
      name: "prefix",
      description: "Display prefix",
      options: [
        {
          name: ["-g", "--global"],
          description: "Print the global prefix to standard out",
        },
      ],
    },
    {
      name: "profile",
      description: "Change settings on your registry profile",
      subcommands: [
        {
          name: "get",
          description:
            "Display all of the properties of your profile, or one or more specific properties",
          args: {
            name: "property",
            isOptional: true,
            description: "Property name",
          },
          options: [registryOption, jsonOption, parseableOption, otpOption],
        },
        {
          name: "set",
          description: "Set the value of a profile property",
          args: [
            {
              name: "property",
              description: "Property name",
              suggestions: [
                "email",
                "fullname",
                "homepage",
                "freenode",
                "twitter",
                "github",
              ],
            },
            {
              name: "value",
              description: "Property value",
            },
          ],
          options: [registryOption, jsonOption, parseableOption, otpOption],
          subcommands: [
            {
              name: "password",
              description:
                "Change your password. This is interactive, you'll be prompted for your current password and a new password",
            },
          ],
        },
        {
          name: "enable-2fa",
          description: "Enables two-factor authentication",
          args: {
            name: "mode",
            description:
              "Mode for two-factor authentication. Defaults to auth-and-writes mode",
            isOptional: true,
            suggestions: [
              {
                name: "auth-only",
                description:
                  "Require an OTP when logging in or making changes to your account's authentication",
              },
              {
                name: "auth-and-writes",
                description:
                  "Requires an OTP at all the times auth-only does, and also requires one when publishing a module, setting the latest dist-tag, or changing access via npm access and npm owner",
              },
            ],
          },
          options: [registryOption, otpOption],
        },
        {
          name: "disable-2fa",
          description: "Disables two-factor authentication",
          options: [registryOption, otpOption],
        },
      ],
    },
    {
      name: "prune",
      description: "Remove extraneous packages",
      args: {
        name: "[<@scope>/]<pkg>",
        isOptional: true,
      },
      options: [
        omitOption,
        dryRunOption,
        jsonOption,
        {
          name: "--production",
          description: "Remove the packages specified in your devDependencies",
        },
        ...workSpaceOptions,
      ],
    },
    {
      name: "publish",
      description: "Publish a package",
      args: {
        name: "tarball|folder",
        isOptional: true,
        description:
          "A url or file path to a gzipped tar archive containing a single folder with a package.json file inside | A folder containing a package.json file",
        template: ["folders"],
      },
      options: [
        {
          name: "--tag",
          description: "Registers the published package with the given tag",
          args: { name: "tag" },
        },
        ...workSpaceOptions,
        {
          name: "--access",
          description:
            "Sets scoped package to be publicly viewable if set to 'public'",
          args: {
            default: "restricted",
            suggestions: ["restricted", "public"],
          },
        },
        dryRunOption,
        otpOption,
      ],
    },
    {
      name: ["rebuild", "rb"],
      description: "Rebuild a package",
      args: {
        name: "[<@scope>/]<pkg>[@<version>]",
      },
      options: [
        globalOption,
        ...workSpaceOptions,
        ignoreScriptsOption,
        {
          name: "--no-bin-links",
          description:
            "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables",
        },
      ],
    },
    {
      name: "repo",
      description: "Open package repository page in the browser",
      args: {
        name: "package",
        isOptional: true,
        generators: npmSearchGenerator,
        debounce: true,
        isVariadic: true,
      },
      options: [
        ...workSpaceOptions,
        {
          name: "--no-browser",
          description: "Display in command line instead of browser",
          exclusiveOn: ["--browser"],
        },
        {
          name: "--browser",
          description:
            "The browser that is called by the npm repo command to open websites",
          args: { name: "browser" },
          exclusiveOn: ["--no-browser"],
        },
      ],
    },
    {
      name: "restart",
      description: "Restart a package",
      options: [
        ignoreScriptsOption,
        scriptShellOption,
        {
          name: "--",
          args: {
            name: "arg",
            description: "Arguments to be passed to the restart script",
          },
        },
      ],
    },
    {
      name: "root",
      description: "Display npm root",
      options: [
        {
          name: ["-g", "--global"],
          description:
            "Print the effective global node_modules folder to standard out",
        },
      ],
    },
    {
      name: ["search", "s", "se", "find"],
      description: "Search for packages",
      args: {
        name: "search terms",
        isVariadic: true,
      },
      options: [
        longOption,
        jsonOption,
        {
          name: "--color",
          description: "Show colors",
          args: {
            name: "always",
            suggestions: ["always"],
            description: "Always show colors",
          },
          exclusiveOn: ["--no-color"],
        },
        {
          name: "--no-color",
          description: "Do not show colors",
          exclusiveOn: ["--color"],
        },
        parseableOption,
        {
          name: "--no-description",
          description: "Do not show descriptions",
        },
        {
          name: "--searchopts",
          description:
            "Space-separated options that are always passed to search",
          args: {
            name: "searchopts",
          },
        },
        {
          name: "--searchexclude",
          description:
            "Space-separated options that limit the results from search",
          args: {
            name: "searchexclude",
          },
        },
        registryOption,
        {
          name: "--prefer-online",
          description:
            "If true, staleness checks for cached data will be forced, making the CLI look for updates immediately even for fresh package data",
          exclusiveOn: ["--prefer-offline", "--offline"],
        },
        {
          name: "--prefer-offline",
          description:
            "If true, staleness checks for cached data will be bypassed, but missing data will be requested from the server",
          exclusiveOn: ["--prefer-online", "--offline"],
        },
        {
          name: "--offline",
          description:
            "Force offline mode: no network requests will be done during install",
          exclusiveOn: ["--prefer-online", "--prefer-offline"],
        },
      ],
    },
    { name: "set", description: "Sets the config key to the value" },
    {
      name: "set-script",
      description: "Set tasks in the scripts section of package.json",
      args: [
        {
          name: "script",
          description:
            "Name of the task to be added to the scripts section of package.json",
        },
        {
          name: "command",
          description: "Command to run when script is called",
        },
      ],
      options: workSpaceOptions,
    },
    {
      name: "shrinkwrap",
      description: "Lock down dependency versions for publication",
    },
    {
      name: "star",
      description: "Mark your favorite packages",
      args: {
        name: "pkg",
        description: "Package to mark as favorite",
      },
      options: [
        registryOption,
        {
          name: "--no-unicode",
          description: "Do not use unicode characters in the tree output",
        },
      ],
    },
    {
      name: "stars",
      description: "View packages marked as favorites",
      args: {
        name: "user",
        isOptional: true,
        description: "View packages marked as favorites by <user>",
      },
      options: [registryOption],
    },
    {
      name: "start",
      description: "Start a package",
      options: [
        ignoreScriptsOption,
        scriptShellOption,
        {
          name: "--",
          args: {
            name: "arg",
            description: "Arguments to be passed to the start script",
          },
        },
      ],
    },
    {
      name: "stop",
      description: "Stop a package",
      options: [
        ignoreScriptsOption,
        scriptShellOption,
        {
          name: "--",
          args: {
            name: "arg",
            description: "Arguments to be passed to the stop script",
          },
        },
      ],
    },
    {
      name: "team",
      description: "Manage organization teams and team memberships",
      subcommands: [
        {
          name: "create",
          args: { name: "scope:team" },
          options: [registryOption, otpOption],
        },
        {
          name: "destroy",
          args: { name: "scope:team" },
          options: [registryOption, otpOption],
        },
        {
          name: "add",
          args: [{ name: "scope:team" }, { name: "user" }],
          options: [registryOption, otpOption],
        },
        {
          name: "rm",
          args: [{ name: "scope:team" }, { name: "user" }],
          options: [registryOption, otpOption],
        },
        {
          name: "ls",
          args: { name: "scope|scope:team" },
          options: [registryOption, jsonOption, parseableOption],
        },
      ],
    },
    {
      name: ["test", "tst", "t"],
      description: "Test a package",
      options: [ignoreScriptsOption, scriptShellOption],
    },
    {
      name: "token",
      description: "Manage your authentication tokens",
      subcommands: [
        {
          name: "list",
          description: "Shows a table of all active authentication tokens",
          options: [jsonOption, parseableOption],
        },
        {
          name: "create",
          description: "Create a new authentication token",
          options: [
            {
              name: "--read-only",
              description:
                "This is used to mark a token as unable to publish when configuring limited access tokens with the npm token create command",
            },
            {
              name: "--cidr",
              description:
                "This is a list of CIDR address to be used when configuring limited access tokens with the npm token create command",
              isRepeatable: true,
              args: {
                name: "cidr",
              },
            },
          ],
        },
        {
          name: "revoke",
          description:
            "Immediately removes an authentication token from the registry. You will no longer be able to use it",
          args: { name: "idtoken" },
        },
      ],
      options: [registryOption, otpOption],
    },
    uninstallSubcommand("uninstall"),
    uninstallSubcommand(["r", "rm"]),
    uninstallSubcommand("un"),
    uninstallSubcommand("remove"),
    uninstallSubcommand("unlink"),
    {
      name: "unpublish",
      description: "Remove a package from the registry",
      args: {
        name: "[<@scope>/]<pkg>[@<version>]",
      },
      options: [
        dryRunOption,
        {
          name: ["-f", "--force"],
          description:
            "Allow unpublishing all versions of a published package. Removes various protections against unfortunate side effects, common mistakes, unnecessary performance degradation, and malicious input",
          isDangerous: true,
        },
        ...workSpaceOptions,
      ],
    },
    {
      name: "unstar",
      description: "Remove an item from your favorite packages",
      args: {
        name: "pkg",
        description: "Package to unmark as favorite",
      },
      options: [
        registryOption,
        otpOption,
        {
          name: "--no-unicode",
          description: "Do not use unicode characters in the tree output",
        },
      ],
    },
    {
      name: ["update", "upgrade", "up"],
      description: "Update a package",
      options: [
        { name: "-g", description: "Update global package" },
        {
          name: "--global-style",
          description:
            "Causes npm to install the package into your local node_modules folder with the same layout it uses with the global node_modules folder",
        },
        {
          name: "--legacy-bundling",
          description:
            "Causes npm to install the package such that versions of npm prior to 1.4, such as the one included with node 0.8, can install the package",
        },
        {
          name: "--strict-peer-deps",
          description:
            "If set to true, and --legacy-peer-deps is not set, then any conflicting peerDependencies will be treated as an install failure",
        },
        {
          name: "--no-package-lock",
          description: "Ignores package-lock.json files when installing",
        },
        omitOption,
        ignoreScriptsOption,
        {
          name: "--no-audit",
          description:
            "Submit audit reports alongside the current npm command to the default registry and all registries configured for scopes",
        },
        {
          name: "--no-bin-links",
          description:
            "Tells npm to not create symlinks (or .cmd shims on Windows) for package executables",
        },
        {
          name: "--no-fund",
          description:
            "Hides the message at the end of each npm install acknowledging the number of dependencies looking for funding",
        },
        {
          name: "--save",
          description:
            "Update the semver values of direct dependencies in your project package.json",
        },
        dryRunOption,
        ...workSpaceOptions,
      ],
    },
    {
      name: "version",
      description: "Bump a package version",
      options: [
        ...workSpaceOptions,
        jsonOption,
        {
          name: "--allow-same-version",
          description:
            "Prevents throwing an error when npm version is used to set the new version to the same value as the current version",
        },
        {
          name: "--no-commit-hooks",
          description:
            "Do not run git commit hooks when using the npm version command",
        },
        {
          name: "--no-git-tag-version",
          description:
            "Do not tag the commit when using the npm version command",
        },
        {
          name: "--preid",
          description:
            'The "prerelease identifier" to use as a prefix for the "prerelease" part of a semver. Like the rc in 1.2.0-rc.8',
          args: {
            name: "prerelease-id",
          },
        },
        {
          name: "--sign-git-tag",
          description:
            "If set to true, then the npm version command will tag the version using -s to add a signature",
        },
      ],
    },
    {
      name: ["view", "v", "info", "show"],
      description: "View registry info",
      options: [...workSpaceOptions, jsonOption],
    },
    {
      name: "whoami",
      description: "Display npm username",
      options: [registryOption],
    },
  ],
};

export default completionSpec;
